为什么不能直接要密钥
普通做法让你把 sk-xxxx 直接发到聊天/命令里,这有三个问题:
- 明文留在对话和日志里,谁都能看到。
- 命令历史/进程列表可能捕获参数里的密钥。
- 共享出去的截图/转录会泄露。
OpenClaw 的 secrets 工具把这条路改成了:AI 只请求"缺哪个密钥、干什么用",你用掩码录入,值直送托管存储,模型自己都读不到明文。
secrets 的三个动作
| 动作 | 用途 |
|------|------|
| list | 先看已有哪些密钥的元信息(只看名字/用途,不看值) |
| request | 向用户申请一个缺失的密钥(用户掩码录入,值进存储) |
| delete | 删除某个条目 |
第一步:先 list,别重复申请
secrets({ action: "list" })
返回的是元数据(名字、allowedHosts、用途),不含值。如果已经有同用途的密钥,直接复用,不要重复申请。
第二步:request 缺失的密钥
secrets({
action: "request",
name: "OPENAI_API_KEY",
kind: "secret",
reason: "调用 OpenAI 图像接口生成文章封面",
allowedHosts: ["api.openai.com"]
})
调用后,聊天里会出现一个掩码录入框,你填入真实密钥。值不会出现在任何聊天文本、命令参数或日志里,直接进托管 SecretRef 存储。
#### allowedHosts 的重要性
allowedHosts 声明这个密钥"只能被发往哪些主机"。即使将来有恶意指令想把它发到别处,网关会拦截——这是纵深防御,不是形式主义。只填真实需要的域名,如 api.openai.com、dashscope.aliyuncs.com。
第三步:用返回的 SecretRef 落地配置
request 成功后,工具返回一个 SecretRef(如 ${OPENAI_API_KEY} 形式的引用)。把这个引用写进配置字段,而不是把真实值写进去:
- 支持的配置项会识别 SecretRef,运行时自动用真实值替换。
- 网关自己跑的命令,则用自动注入的不透明环境变量,AI 看不到、打印不出来。
关键行为(必须知道)
- AI 永远读不到值:
request返回的是引用,不是明文。这是设计铁律。
- list 只给元数据:已经存在的密钥,先查再决定要不要新申请。
- allowedHosts 为空 = 禁止外发:如果你没填,密钥只能被本机配置引用,无法 egress 替换,安全但不便——按需填真实域名。
- Operator 设的环境变量可读但不应申请:已有的 env 条目是人工配的,别用
request去覆盖它。
标准工作流
- 脚本要一个密钥 → 先
secrets({action:"list"})查重。
- 没有 →
secrets({action:"request", name, reason, allowedHosts})。
- 用户在掩码框录入 → 拿到
SecretRef。
- 把
SecretRef写进对应配置字段,重新跑任务。
踩坑与反模式
- ❌ 在聊天里发
sk-xxx:明文泄露,且违反安全策略。走request。
- ❌ **allowedHosts 乱填
***:等于放弃主机白名单保护,按需精确填。
- ❌ 重复申请相同密钥:先
list复用,别每跑一次都要用户录一遍。
- ✅ 所有外部 API Key 一律走 secrets:模型、日志、截图里都不会出现明文。
和 exec 的分工
- 本机 shell / node / sandbox:没有 SecretRef 自动注入机制,密钥需手动管理(更危险)。
- 网关托管命令:自动注入 opaque env,AI 不可见,最安全。
一句话总结
要密钥别张口要明文。先 list 查重,再 request 让用户掩码录入,拿 SecretRef 写配置——AI、日志、截图里全程零明文。
扫码关注公众号,获取更多 OpenClaw 实操技巧:
